﻿// recvfrom_Server.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include <iostream>

#include <Windows.h>

#include <winsock.h>
#pragma comment(lib,"ws2_32.lib")

/*
客户端发送给服务器之后，服务器在发回客户端
    
    如果想知道数据是谁发来的  用recvfrom     接收数据的同时 把发送方的协议族接收到
    recvfrom == recv + accept


    可以把tcp比作    打电话     是稳定的数据传输通道
        把udp比作    发短信     不知道对方有没有收到，只知道自己发送过去了


*/

/*
tcp协议和udp协议的区别：
    1. 通信媒介
	    tcp  数据流   udp数据报文
	2. tcp有连接  udp无连接
	3. tcp数据有序，udp无序
		tcp先发先到   udp有可能后发先到
	4. tcp数据无边界  udp数据有边界
		一堆沙子			一包一包的  两包之间分隔开
	5. tcp可靠 udp不可靠
		udp发送的数据有可能丢失
	6. tcp安全 udp不安全
		udp被别人窃取了数据也有可能
	7. tcp慢  udp快
	8. tcp有中心 udp无中心

*/

/*
tcp协议详情：
    建立连接的步骤：三次握手

    断开连接的步骤：四次挥手


    tcp协议的十一种状态：
        CLOSED ： 最开始的情况 以及最终的情况
            还没建立连接    断开了连接之后
        ESTABLISHED： 建立了稳定的数据传输通道
        LISTEN：监听
        SYN_RECV：服务器收到SYN后发ACK之前
        SYN_SEND：发送SYN后  接收ACK之前
        CLOSE_WAIT：客户端收到FIN后，发送ACK之前
        LAST_ACK：客户端被动关闭   发送ACK之后
        TIME_WAIT：延时等待状态   数据收发已完成 已发送FIN 等待ACK超时 自动跳转到CLOSED
        FIN_WAIT1：收到客户端发来的FIN后，发送ACK之前
            要收一些数据
        CLOSING：同时关闭    差不多同时发送FIN 等着收ACK就好
        FIN_WAIT2：发送ACK之后  发送SYN之前
            要发送一些数据


*/

/*
osi模型：开放式系统互联通信参考模型
    Open  System Interconnection   Refrence  Model

物理层：     物理设备        邮局里的搬运工
数据链路层： 分流			    邮局里的拆装箱工人
网络层：		数据在大的网络中传输，要分开   区分发送到哪台电脑
                        IP协议
传输层：		数据传输     TCP协议 UDP http https smtp
会话：   数据传输中设置和维护两台主机之间的连接
表示：	数据格式转化
    二进制  转 字符   视频  音频
应用：  提供为应用软件设置的接口



物理层
数据链路层
网络层
传输层
应用层

*/

/*
tcp的流量控制及滑动窗口机制：
    发送缓冲区和接受缓冲区

    如果发送端和接收端速度不匹配（发送快，接收慢）可能导致数据丢失
    所以需要流量控制手段：
        滑动窗口机制

*/

/*
粘包：tcp才会粘包，UDP不会
    发送方  发送了三个包裹    接收方接受到的时候 三个包裹混淆了

tcp导致粘包的原因：
    1. 发送方
        为了提升效率  把一堆数据整理到一起再发送  也有可能太长的数据拆分成多条发送
        导致有三种可能：
            1.1 多条数据被整合成一条发送
            1.2 长数据被拆分成多段分开发送
            1.3 长数据被拆分的段和短数据一起发送
    2. 接收方
        如果不是很快的速度把数据从缓冲区中读走
        就会导致数据粘到一起。

解决方案：
    方案一：
        数据格式化
        发送的数据约定好固定格式（结构体）
        约定固定的发送长度
        添加区分的头

    方案二：
        发送方发送一段 停顿一点时间

    方案三：
        动态缓冲区暂存    空间换时间
        接收方收到数据时先放到缓冲区中

    C  文件操作
        fscanf  经常出问题

*/
int main()
{
    //0.确定协议版本信息；
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (2 != LOBYTE(wsaData.wVersion) ||
        2 != HIBYTE(wsaData.wVersion))
    {
        printf("确定协议版本信息失败%d\n", GetLastError());
        return -1;
    }
    printf("确定协议版本信息成功\n");


    //1.创建socket
    SOCKET serverSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (SOCKET_ERROR == serverSocket)
    {
        printf("创建socket失败:%d\n", GetLastError());

        //6.清理协议版本信息：
        WSACleanup();


        return -1;
    }
    printf("创建socket成功\n");


    //2.确定服务器的协议地址族：
    SOCKADDR_IN sAddr = { 0 };
    sAddr.sin_family = AF_INET;
    sAddr.sin_addr.S_un.S_addr = inet_addr("10.133.98.104");
    sAddr.sin_port = htons(6666);


    //3.绑定
    int r = bind(serverSocket, (sockaddr*)&sAddr, sizeof(sAddr));
    if (-1 == r)
    {
        printf("绑定失败:%d\n", GetLastError());

        //5.关闭socket：
        closesocket(serverSocket);

        //6.清理协议版本信息：
        WSACleanup();

        return -1;
    }
    printf("绑定成功\n");

    //4.通信
    char buff[256];
    int ret;

    //用来存储客户端的协议地址族：
    SOCKADDR_IN  cAddr = { 0 };
    int len = sizeof(cAddr);


    while (1)
    {
        r = recvfrom(serverSocket, buff, strlen(buff), NULL, (sockaddr*)&cAddr, &len);
        if (r > 0)
        {
            buff[r] = 0;
            printf("客户端发来%s 地址族：%s  端口号：%d\n", buff,inet_ntoa(cAddr.sin_addr),htonl(cAddr.sin_port));

            ret = sendto(serverSocket, "服务器收到并回复谢谢", strlen("服务器收到并回复谢谢"), NULL, (sockaddr*)&cAddr, sizeof(cAddr));
            printf("ret:%d\n", ret);

        }
    }


    //5.关闭socket
    closesocket(serverSocket);


    //6.清理协议版本信息：
    WSACleanup();

    return 0;
}
